﻿using MiniExcelLibs;
using System.Text.RegularExpressions;
using System.Collections.Concurrent;
using DotLiquid;
using Newtonsoft.Json.Linq;
using System.Text;

namespace excel2bean
{
    public class Exporter
    {

        public Exporter(string inputPath, string assetsPath, string bytesPath, string scriptsPath)
        {
            SetPath(inputPath, assetsPath, bytesPath, scriptsPath);
        }

        /// <summary>
        /// 接受的文件后缀
        /// </summary>
        private static readonly HashSet<string> _accepts = new HashSet<string>()
        {
            ".xlsx",".xls"
        };

        /// <summary>
        /// 工作簿名称限制正则表达式（全英文）
        /// </summary>
        private static readonly Regex _regex = new Regex("^[A-Za-z]+$");

        #region 日志

        /// <summary>
        /// 输出日志
        /// </summary>
        public readonly ConcurrentQueue<string> outputs = new ConcurrentQueue<string>();

        /// <summary>
        /// 当前阶段
        /// </summary>
        public string stage;

        /// <summary>
        /// 总数
        /// </summary>
        public int total;

        /// <summary>
        /// 完成数
        /// </summary>
        public int value;

        #endregion

        /// <summary>
        /// 输入路径
        /// </summary>
        private string inputPath;

        /// <summary>
        /// 输出目标项目资源地址
        /// </summary>
        private string assetsPath;

        /// <summary>
        /// 二进制数据地址
        /// </summary>
        private string bytesPath;

        /// <summary>
        /// 脚本地址
        /// </summary>
        private string scriptsPath;

        private string finalBytesPath;

        private string finalScriptsPath;

        public void SetPath(string inputPath, string assetsPath, string bytesPath, string scriptsPath)
        {
            this.inputPath = inputPath;
            this.assetsPath = assetsPath;
            this.bytesPath = bytesPath;
            this.scriptsPath = scriptsPath;

            finalBytesPath = Path.Combine(assetsPath, "Resources", bytesPath);
            finalScriptsPath = Path.Combine(assetsPath, "Scripts", scriptsPath);
        }

        /// <summary>
        /// 导出文件
        /// </summary>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        /// <exception cref="DirectoryNotFoundException"></exception>
        /// <exception cref="FileNotFoundException"></exception>
        public async Task Export()
        {
            stage = string.Empty;

            if (string.IsNullOrEmpty(inputPath) || string.IsNullOrEmpty(assetsPath))
            {
                throw new ArgumentException("输入/输出路径不能为空");
            }

            DirectoryInfo inputDir = new DirectoryInfo(inputPath);
            if (!inputDir.Exists)
            {
                throw new DirectoryNotFoundException("输入路径不存在");
            }

            //筛选文件
            FileInfo[] files = FilterFiles(inputDir);

            //扫描文件
            List<Parser> results = await ScanFiles(files);

            //去重
            List<Parser> parsers = Distinct(results);

            await WriteFiles(parsers);
        }

        /// <summary>
        /// 过滤文件后缀
        /// </summary>
        /// <param name="inputDir"></param>
        /// <returns></returns>
        /// <exception cref="FileNotFoundException"></exception>
        private FileInfo[] FilterFiles(DirectoryInfo inputDir)
        {
            FileInfo[] files = inputDir.GetFiles();

            stage = "筛选文件";
            total = files.Length;
            value = 0;

            //筛选文件格式
            files = files.Where(file =>
            {
                bool v = !file.Name.Contains("~") && _accepts.Contains(file.Extension);
                value++;
                return v;
            }).ToArray();

            if (files.Length == 0)
            {
                throw new FileNotFoundException("没有合法的文件");
            }

            return files;
        }

        /// <summary>
        /// 扫描文件
        /// </summary>
        /// <param name="files"></param>
        /// <returns></returns>
        private async Task<List<Parser>> ScanFiles(FileInfo[] files)
        {
            stage = "扫描文件";
            total = files.Length;
            value = 0;

            List<Parser> results = new List<Parser>();

            //遍历输入文件
            for (int i = 0; i < files.Length; i++)
            {
                //Task.Run是异步操作，在Lambda里面访问i可能是已被更新过的
                int idx = i;

                //异步处理每个文件
                try
                {
                    List<Parser>? _parsers = await Task.Run(() =>
                    {
                        return ReadFile(files[idx]);
                    });

                    if (_parsers != null)
                    {
                        results.AddRange(_parsers);
                    }
                }
                catch (Exception)
                {
                    throw;
                }
                finally
                {
                    value++;
                }
            }

            return results;
        }

        /// <summary>
        /// 写文件
        /// </summary>
        /// <param name="parsers"></param>
        private async Task WriteFiles(List<Parser> parsers)
        {
            stage = "读取模板文件";
            total = 3;
            value = 0;

            //创建输出文件夹
            CreateOutputDirectory();

            string root = AppDomain.CurrentDomain.BaseDirectory;

            //读取文件模板
            string beanTemplateText = await File.ReadAllTextAsync(Path.Combine(root, "Template/Bean.template"));
            Template beanTemplate = Template.Parse(beanTemplateText);
            value++;

            string containerTemplateText = await File.ReadAllTextAsync(Path.Combine(root, "Template/Container.template"));
            Template containerTemplate = Template.Parse(containerTemplateText);
            value++;

            string dataManagerTemplateText = await File.ReadAllTextAsync(Path.Combine(root, "Template/DataManager.template"));
            value++;


            stage = "输出文件";
            total = parsers.Count + 1;
            value = 0;

            //写datamanager
            using (StreamWriter sw = File.CreateText(Path.Combine(finalScriptsPath, "DataManager.cs")))
            {
                await sw.WriteAsync(dataManagerTemplateText);
                value++;
            }

            //写数据文件
            foreach (Parser parser in parsers)
            {
                try
                {
                    await Write(parser, beanTemplate, containerTemplate);
                }
                catch (Exception e)
                {
                    outputs.Enqueue($"写文件时出错 {e.Message}");
                }
                finally
                {
                    value++;
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private void CreateOutputDirectory()
        {
            try
            {
                Directory.CreateDirectory(Path.Combine(assetsPath, "Resources", bytesPath));
                Directory.CreateDirectory(Path.Combine(assetsPath, "Scripts", scriptsPath, "beans"));
                Directory.CreateDirectory(Path.Combine(assetsPath, "Scripts", scriptsPath, "containers"));
            }
            catch (Exception e)
            {
                outputs.Enqueue($"创建输出文件夹失败 {e.Message}");
            }
        }

        /// <summary>
        /// 去掉重名工作簿
        /// </summary>
        /// <param name="results"></param>
        /// <returns></returns>
        private List<Parser> Distinct(List<Parser> results)
        {
            Dictionary<string, Parser> map = new Dictionary<string, Parser>();

            List<Parser> distinct = new List<Parser>();

            foreach (Parser parser in results)
            {
                string key = parser.sheet;
                if (map.TryGetValue(key, out Parser? p))
                {
                    outputs.Enqueue($"工作簿名：[{key}]在[{parser.path}]  [{p.path}]里有重复 跳过处理");

                    distinct.Remove(p);
                    map.Remove(key);
                }
                else
                {
                    distinct.Add(parser);
                    map.Add(key, parser);
                }
            }

            return distinct;
        }

        /// <summary>
        /// 读取单个文件
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        private async Task<List<Parser>?> ReadFile(FileInfo file)
        {
            string path = file.FullName;

            string[] sheets = sheets = MiniExcel.GetSheetNames(path)
               .Where(sheet =>
               {
                   //工作簿名称筛选
                   //1.不以默认Sheet开头
                   //2.全英文组成
                   return !sheet.StartsWith("Sheet") && _regex.IsMatch(sheet);
               })
               .ToArray();

            if (sheets.Length == 0)
            {
                return null;
            }

            List<Parser> parsers = new List<Parser>(sheets.Length);

            for (int i = 0; i < sheets.Length; i++)
            {
                string sheet = sheets[i];
                try
                {
                    Parser parser = await Task.Run(() =>
                    {
                        return Parser.Make(path, sheet);
                    });
                    parsers.Add(parser);
                }
                catch (Exception e)
                {
                    outputs.Enqueue($"读取文件时出错:{e.Message}");
                }
            }

            return parsers;
        }

        /// <summary>
        /// parser导出文件
        /// </summary>
        /// <param name="parser"></param>
        private async Task Write(Parser parser, Template beanTemplate, Template containerTemplate)
        {
            (JArray jroot, Dictionary<string, Parser.FieldType> types) kv = parser.Parse();

            Dictionary<string, Parser.FieldType> types = kv.types;

            await WriteBytes(parser, kv.jroot);

            await WriteBean(parser, types, beanTemplate);

            await WriteContainer(parser, types, containerTemplate);
        }

        /// <summary>
        /// 写数据文件
        /// </summary>
        /// <param name="parser"></param>
        /// <param name="jroot"></param>
        private async Task WriteBytes(Parser parser, JArray jroot)
        {
            //数据文件路径
            string bytesFilePath = Path.Combine(finalBytesPath, $"{parser.sheet}.bytes");
            using (FileStream fs = File.Create(bytesFilePath))
            {
                string json = jroot.ToString();
                //json转成utf8编码二进制数组
                byte[] bytes = Encoding.UTF8.GetBytes(json);

                //加密
                Encrypt(ref bytes);

                //数据写文件
                await fs.WriteAsync(bytes, 0, bytes.Length);
            }
        }

        /// <summary>
        /// 写bean文件
        /// </summary>
        /// <param name="parser"></param>
        /// <param name="types"></param>
        /// <param name="beanTemplate"></param>
        /// <returns></returns>
        private async Task WriteBean(Parser parser, Dictionary<string, Parser.FieldType> types, Template beanTemplate)
        {
            //写bean文件
            Hash hash = Hash.FromAnonymousObject(new
            {
                desc = parser.GetDesc(),
                parser.sheet,
                fields = types
            });

            string beanFileContent = beanTemplate.Render(hash);

            string beanFilePath = Path.Combine(finalScriptsPath, "beans", $"{parser.sheet}Bean.cs");
            using (FileStream fs = File.Create(beanFilePath))
            {

                byte[] bytes = Encoding.UTF8.GetBytes(beanFileContent);

                //数据写文件
                await fs.WriteAsync(bytes, 0, bytes.Length);
            }
        }

        /// <summary>
        /// 写container
        /// </summary>
        /// <param name="parser"></param>
        /// <param name="types"></param>
        /// <param name="containerTemplate"></param>
        private async Task WriteContainer(Parser parser, Dictionary<string, Parser.FieldType> types, Template containerTemplate)
        {
            //写container文件
            Hash hash = Hash.FromAnonymousObject(new
            {
                desc = parser.GetDesc(),
                parser.sheet,
                bytesPath,
                parser.majorKey,
                majorKeyType = types[parser.majorKeyCol].content
            });

            string containerFileContent = containerTemplate.Render(hash);

            string containerFilePath = Path.Combine(finalScriptsPath, "containers", $"{parser.sheet}Container.cs");
            using (FileStream fs = File.Create(containerFilePath))
            {

                byte[] bytes = Encoding.UTF8.GetBytes(containerFileContent);

                //数据写文件
                await fs.WriteAsync(bytes, 0, bytes.Length);
            }
        }


        /// <summary>
        /// 数据加密
        /// 这里写了个简单的取反加密
        /// </summary>
        /// <param name="bytes"></param>
        private static void Encrypt(ref byte[] bytes)
        {
            for (int i = 0; i < bytes.Length; i++)
            {
                bytes[i] = (byte)(~bytes[i]);
            }
        }
    }
}
